﻿using System.Collections.Generic;
using System.Reflection;
using System.Text;
using smp.QueryDb.Structure;

namespace smp.QueryDb
{
    internal class FilterStructure
    {
        private Column _column;
        private PropertyInfo _property;
        private string _comparitor;
        private StringComparitor? _stringComparitor;
        private object _value;
        private IList<object> _values;
        private readonly List<FilterStructure> _additionalFilters = new List<FilterStructure>();

        internal FilterStructure(Column column)
        {
            _column = column;
            Parameters = new List<IParameter>();
        }

        internal FilterStructure(PropertyInfo property)
        {
            _property = property;
            Parameters = new List<IParameter>();
        }

        internal bool HasBaseOperation { get { return _column != null || _property != null; } }

        internal List<IParameter> Parameters { get; private set; }

        internal string Logic { get; private set; }

        internal void SetLogic(string logic)
        {
            Logic = logic;
        }

        internal void AddAdditionalFilter(FilterStructure filterStructure)
        {
            _additionalFilters.Add(filterStructure);
        }

        internal void SetOperation(string comparitor, object value)
        {
            _comparitor = comparitor;
            _value = value;
        }

        internal void SetInOperation(IList<object> values)
        {
            _comparitor = "IN";
            _values = values;
        }

        internal void SetStringOperation(StringComparitor comparitor, object value)
        {
            _stringComparitor = comparitor;
            _comparitor = "LIKE";
            _value = value;
        }

        internal enum StringComparitor
        {
            Contains,
            StartsWith,
            EndsWith
        }

        internal void AppendStatementBuilder(ref int index, Table table, List<Map> maps,  StringBuilder statementBuilder, IDbAdapter adapter)
        {
            if (_column == null && _property == null)
                return;

            SetAndValidateColumn(table, maps);
            if (Parameters == null)
            {
                Parameters = new List<IParameter>();
            }

            BuildStatement(ref index,  statementBuilder, adapter);
            if (_additionalFilters != null)
            {
                foreach (var filter in _additionalFilters)
                {
                    statementBuilder.Insert(0, "(");
                    statementBuilder.AppendFormat(" {0} ", filter.Logic);
                    filter.AppendStatementBuilder(ref index, table, maps,  statementBuilder, adapter);
                    if(filter.Parameters != null)
                    {
                        Parameters.AddRange(filter.Parameters);
                    }
                    statementBuilder.Append(") ");
                }
            }
        }

        internal void AppendExecuteCommandBuilder( Command command,  int index, Table table, IDbAdapter adapter, int whereClauseIndex)
        {
            if(_column == null)
                return;
            if(!table.IsTableDeclared(_column.TableDeclaration))
            {
                throw Helper.Error("Invalid filter for execution command, column {0} does not belong to table {1}",
                                   _column.Name,
                                   table.Declaration);
            }

            BuildStatement(ref index, command.CommandBuilder, adapter);
            if (_additionalFilters != null)
            {
                foreach (var filter in _additionalFilters)
                {
                    command.CommandBuilder.Insert(whereClauseIndex, "(");
                    command.CommandBuilder.AppendFormat(" {0} ", filter.Logic);
                    filter.AppendExecuteCommandBuilder(command, index, table, adapter, whereClauseIndex);
                    if (filter.Parameters != null)
                    {
                        Parameters.AddRange(filter.Parameters);
                    }
                    command.CommandBuilder.Append(") ");
                }
            }
        }

        internal void ResetParameters()
        {
            if(Parameters != null)
            {
                Parameters.Clear();
            }
            if (_additionalFilters != null)
            {
                foreach (var additionalFilter in _additionalFilters)
                {
                    additionalFilter.ResetParameters();
                }
            }
        }

        private void SetAndValidateColumn(Table table, List<Map> maps)
        {
            if (_column == null)
            {
                if (_property == null)
                    return;

                var mapColumn = Helper.GetMapColumn(_property, maps);
                if (mapColumn == null)
                {
                    throw Helper.Error("Invalid Where property : {0}, a map for the column and property are not set",
                        _property.Name);
                }
                _column = mapColumn;
            }

            if (!table.IsTableDeclared(_column.TableDeclaration))
            {
                throw Helper.Error("Invalid Where column : {0}, table declaration {1} is not defined in From call.",
                             _column.Name,
                             _column.TableDeclaration);
            }
        }

        private void BuildStatement(ref int index, StringBuilder statementBuilder, IDbAdapter adapter)
        {
            //if is null, just text
            //if in then in()
            //else x = y
            if (_value == null && _values == null)
            {
                statementBuilder.AppendFormat(" ({0} {1})", _column.Declaration, _comparitor);
                return;
            }

            string parameterName;

            if (_values != null)
            {
                statementBuilder.AppendFormat(" ({0} {1} (", _column.Declaration, _comparitor);
                foreach (var value in _values)
                {
                    parameterName = string.Format("{0}P_{1}", adapter.ParameterPix, index);
                    statementBuilder.AppendFormat("{0},", parameterName);
                    Parameters.Add(new Parameter(parameterName, _column.DataType, value));
                    index++;
                }
                statementBuilder.Append("))");
                return;
            }

            if (_stringComparitor.HasValue)
            {
                if (_value == null)
                {
                    throw Helper.Error("Null string comparison value for column {0}", _column.Declaration);
                }

                switch (_stringComparitor.Value)
                {
                    case StringComparitor.Contains:
                        _value = "%" + _value + "%";
                        break;
                    case StringComparitor.StartsWith:
                        _value = _value + "%";
                        break;
                    case StringComparitor.EndsWith:
                        _value = "%" + _value;
                        break;
                    default:
                        throw Helper.Error("Invaid string comparison where clause for column {0}", _column.Declaration);
                }
            }

            parameterName = string.Format("{0}P_{1}", adapter.ParameterPix, index);
            statementBuilder.AppendFormat(" ({0} {1} {2})", _column.Declaration, _comparitor, parameterName);
            Parameters.Add(new Parameter(parameterName, _column.DataType, _value));
            index++;
        }
    }
}
